package service

import (
	"context"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"jk-time/webook/internal/domain"
	"jk-time/webook/internal/events/article"
	"jk-time/webook/internal/repository"
	"time"
)

//go:generate mockgen -source=./article.go -package=mocks -destination=mocks/article.mock.go ArticleService
type ArticleService interface {
	Save(ctx context.Context, art domain.Article) (int64, error)
	Withdraw(ctx context.Context, art domain.Article) error
	Publish(ctx context.Context, art domain.Article) (int64, error)
	GetById(ctx context.Context, id int64) (domain.Article, error)
	GetPublishById(ctx context.Context, id int64) (domain.PublishedArticle, error)
	List(ctx *gin.Context, author int64, offset int, limit int) ([]domain.Article, error)
	// 7天内数据
	ListPub(ctx context.Context, start time.Time, offset int, limit int) ([]domain.PublishedArticle, error)
}
type articleService struct {
	// 1. 在 service 这一层使用两个 repository
	authorRepo repository.ArticleAuthorRepository
	readerRepo repository.ArticleReaderRepository

	// 2. 在 repo 里面处理制作库和线上库
	// 1 和 2 是互斥的，不会同时存在
	repo repository.ArticleRepository

	producer articleEvents.Producer
}

func NewArticleService(repo repository.ArticleRepository, authorRepo repository.ArticleAuthorRepository,
	readerRepo repository.ArticleReaderRepository, producer articleEvents.Producer) ArticleService {
	return &articleService{repo: repo, authorRepo: authorRepo, readerRepo: readerRepo, producer: producer}
}
func NewArticleServiceV1(
	authorRepo repository.ArticleAuthorRepository,
	readerRepo repository.ArticleReaderRepository) ArticleService {
	return &articleService{
		authorRepo: authorRepo,
		readerRepo: readerRepo,
	}
}
func NewArticleServiceV2(repo repository.ArticleRepository) ArticleService {
	return &articleService{repo: repo}
}
func (a *articleService) Save(ctx context.Context, art domain.Article) (int64, error) {
	art.Status = domain.ArticleStatusUnpublished
	if art.Id > 0 {
		err := a.repo.Update(ctx, art)
		return art.Id, err
	}
	return a.repo.Create(ctx, art)
}
func (a *articleService) Publish(ctx context.Context, art domain.Article) (int64, error) {
	art.Status = domain.ArticleStatusPublished
	return a.repo.Sync(ctx, art)
}

// PublishV1 基于使用两种 repository 的写法
func (a *articleService) PublishV1(ctx context.Context, art domain.Article) (int64, error) {
	art.Status = domain.ArticleStatusPublished
	var err error
	if art.Id == 0 {
		art.Id, err = a.authorRepo.Create(ctx, art)
	} else {
		err = a.authorRepo.Update(ctx, art)
	}
	if err != nil {
		return 0, err
	}
	for i := 0; i < 3; i++ {
		err := a.readerRepo.Save(ctx, art)
		if err == nil {
			break
		}
		zap.L().Error("部分失败，保存到线上库失败", zap.Int64("art_id", art.Id), zap.Error(err))
	}
	if err != nil {
		zap.L().Error("部分失败，保存到线上库重试都失败了", zap.Int64("art_id", art.Id), zap.Error(err))
		//接入告警系统，手工处理
		//异步，保存到本地文件
		return 0, err
	}

	//开不了事务，service层面无法判断底层数据库
	return art.Id, nil
}
func (a *articleService) Withdraw(ctx context.Context, art domain.Article) error {
	return a.repo.SyncStatus(ctx, art)
}
func (a *articleService) GetById(ctx context.Context, id int64) (domain.Article, error) {
	return a.repo.GetById(ctx, id)
}
func (a *articleService) GetPublishById(ctx context.Context, id int64) (domain.PublishedArticle, error) {
	pub, err := a.repo.GetPublishById(ctx, id)
	go func() {
		if err == nil {
			er := a.producer.ProduceReadEvent(ctx, articleEvents.ReadEvent{
				Aid: pub.Id,
				Uid: id,
			})
			if er != nil {
				zap.L().Error("发送消息失败", zap.Int64("uid", id), zap.Int64("aid", pub.Id))
			}
		}
	}()
	return pub, err
}
func (a *articleService) List(ctx *gin.Context, author int64, offset int, limit int) ([]domain.Article, error) {
	return a.repo.List(ctx, author, offset, limit)
}
func (a *articleService) ListPub(ctx context.Context, start time.Time, offset int, limit int) ([]domain.PublishedArticle, error) {
	return a.repo.ListPub(ctx, start, offset, limit)
}
